package com.vois.jack.btmgr.devices.F1Dev;


import android.text.TextUtils;
import android.util.Log;

import com.vois.jack.btmgr.util.Logger;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 *
 * Created by Administrator on 2017/8/1.
 *
 */

public class Wl400BleDataPackage {
    private Logger logger = Logger.getLogger(Wl400BleDataPackage.class);
    private final byte NOTIFY_TYPE = 0;
    private final byte REQUEST_TYPE = 1;
    private final byte RESPONSE_TYPE = 2;

    private final byte START_PACKAGE_FLAG = 0;
    private final byte MIDDLE_PACKAGE_FLAG = 1;
    private final byte END_PACKAGE_FLAG = 2;
    private final byte FULL_PACKAGE_FLAG = 3;

    private int mtu;
    private Wl400BleReceiveMessage bleReceiveMessage;

    private byte responseSeq = 0;
    private byte requestSeq = 0;
    private byte notifySeq = 0;
    private Map<String, List<byte[]>> wl400WifiCacheMap = new ConcurrentHashMap<>();
    private Map<String, List<byte[]>> wl400ConfigCacheMap = new ConcurrentHashMap<>();
    private Map<String, List<byte[]>> wl400AckCacheMap = new ConcurrentHashMap<>();

    public Wl400BleDataPackage(Wl400BleReceiveMessage bleReceiveMessage) {
        this.bleReceiveMessage = bleReceiveMessage;
    }

    public Wl400BleDataPackage(int mtu, Wl400BleReceiveMessage bleReceiveMessage) {
        this.mtu = mtu;
        this.bleReceiveMessage = bleReceiveMessage;
    }

    public void setMtu(int mtu) {
        this.mtu = mtu;
    }

    public void reset() {
        wl400WifiCacheMap.clear();
        wl400ConfigCacheMap.clear();
        wl400AckCacheMap.clear();
    }

    public List<byte[]> getWl400WifiInfoData(List<Wl400NetworkConfigData.Wl400WifiInfo> wl400WifiInfoList){
        List<byte[]> resultList = new ArrayList<>();
        if(wl400WifiInfoList != null && wl400WifiInfoList.size() > 0 ){
            try {
                for(Wl400NetworkConfigData.Wl400WifiInfo wl400WifiInfo : wl400WifiInfoList){
                    JSONObject bleData = new JSONObject();
                    bleData.put("t", Wl400NetworkConfigData.NOTIFY_WIFI_INFO_TYPE);
                    String wl400WifiJsonStr = getWl400WifiInfoJsonStr(wl400WifiInfo);
                    bleData.put("b", wl400WifiJsonStr);
                    resultList.addAll(getBleData(notifySeq++, NOTIFY_TYPE, bleData.toString()));
                }
            }catch (Exception ex){
                logger.d( "getWl400WifiInfoData#List#ex: " + ex);
            }
        }
        return resultList;
    }

    private String getWl400WifiInfoJsonStr(Wl400NetworkConfigData.Wl400WifiInfo wl400WifiInfo){
        String resultStr = "";
        try {
            JSONObject wifiInfoObject = new JSONObject();

            wifiInfoObject.put("n", wl400WifiInfo.getSSID());
            wifiInfoObject.put("a", wl400WifiInfo.getBSSID());
            wifiInfoObject.put("p", wl400WifiInfo.getCapabilities());
            wifiInfoObject.put("l", wl400WifiInfo.getLevel());
            wifiInfoObject.put("f", wl400WifiInfo.getFrequency());
            wifiInfoObject.put("c", wl400WifiInfo.isCurrentConnect());

            resultStr = wifiInfoObject.toString();
        } catch (JSONException ex) {
            logger.d( "getWl400WifiInfoJsonStr#ex: " + ex);
        }
        return  resultStr;
    }

    public Wl400NetworkConfigData.Wl400WifiInfo getWl400WifiInfoByJsonStr(String str){
        try {
            if(!TextUtils.isEmpty(str)){
                JSONObject wifiInfoObject = new JSONObject(str);

                String SSID = wifiInfoObject.getString("n");
                String BSSID = wifiInfoObject.getString("a");
                int cap = wifiInfoObject.getInt("p");
                int level = wifiInfoObject.getInt("l");
                int fre = wifiInfoObject.getInt("f");
                int isCurrentConnect = wifiInfoObject.getInt("c");

                return new Wl400NetworkConfigData.Wl400WifiInfo(SSID, BSSID, cap, level, fre, isCurrentConnect);
            }
        }catch (Exception ex){
            logger.d( "getWl400WifiInfoByJsonStr#ex: " + ex);
        }
        return  null;
    }

    public List<byte[]> getNotifyData(int type){
        List<byte[]> arrayList = new ArrayList<>();try {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("t", type);
            arrayList = getBleData(notifySeq++, NOTIFY_TYPE, jsonObject.toString());
        }catch (Exception ex){
            logger.d( "getWl400ConfigData#ex: " + ex);
        }
        return  arrayList;
    }

    public List<byte[]> getWl400ConfigData(int configType, Wl400NetworkConfigData.Wl400NetworkConfigInfo wl400NetworkConfigInfo){
        List<byte[]> arrayList = new ArrayList<>();
        try {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("t", configType);
            if(wl400NetworkConfigInfo != null) {
                String wl400ConfigJsonStr = getWl400NetworkConfigJsonStr(wl400NetworkConfigInfo);
                jsonObject.put("b", wl400ConfigJsonStr);
            }else{
                jsonObject.put("b", "");
            }
            arrayList = getBleData(requestSeq++, REQUEST_TYPE, jsonObject.toString());
        }catch (Exception ex){
            logger.d( "getWl400ConfigData#ex: " + ex);
        }
        return  arrayList;
    }

    private String getWl400NetworkConfigJsonStr(Wl400NetworkConfigData.Wl400NetworkConfigInfo wl400NetworkConfigInfo) {
        String resultStr = "";
        try {
            JSONObject wl400ConfigObject = new JSONObject();
            wl400ConfigObject.put("n", wl400NetworkConfigInfo.getName());
            wl400ConfigObject.put("p", wl400NetworkConfigInfo.getPassword());
            resultStr = wl400ConfigObject.toString();
        } catch (JSONException ex) {
            logger.d( "getWl400NetworkConfigJsonStr#ex: " + ex);
        }
        return  resultStr;
    }

    public Wl400NetworkConfigData.Wl400NetworkConfigInfo getWl400NetworkConfigByJsonStr(String str){
        try {
            if(!TextUtils.isEmpty(str)){
                JSONObject wl400BleCfgObject = new JSONObject(str);
                String name = wl400BleCfgObject.getString("n");
                String password = wl400BleCfgObject.getString("p");
                return new Wl400NetworkConfigData.Wl400NetworkConfigInfo( name, password);
            }
        }catch (Exception ex){
            logger.d( "getWl400NetworkConfigByJsonStr#ex: " + ex);
        }
        return  null;
    }

    public List<byte[]> getWl400ResponseData(int type){
        List<byte[]> arrayList = new ArrayList<>();
        try {
            JSONObject bleData = new JSONObject();
            bleData.put("t", type);
            bleData.put("b", "");
            arrayList = getBleData(responseSeq++, RESPONSE_TYPE, bleData.toString());
        }catch (Exception ex){
            logger.d( "getWl400ResponseData#ex:" + ex);
        }
        return  arrayList;
    }

    private List<byte[]> fragmentByte(byte[] data, int fragmentLen) {
        final int limit = fragmentLen;
        List<byte[]> result = new ArrayList<>(limit);
        if(data != null && data.length > 0){
            int pos = 0;
            final int textLen = data.length;
            while (pos < textLen) {
                int nextPos = pos + Math.min(limit, textLen - pos);
                if ((nextPos <= pos) || (nextPos > textLen)) {
                    break;
                }
                byte[] bytes = new byte[(nextPos-pos)];
                System.arraycopy(data, pos, bytes, 0, bytes.length);
                result.add(bytes);
                pos = nextPos;
            }
        }
        return result;
    }

    private List<byte[]> getBleData(final byte seq, final byte type, final String str){
        final int limit = (mtu - 5);
        List<byte[]> arrayList = new ArrayList<>();
        if(!TextUtils.isEmpty(str)){
            byte flag;
            byte num;
            final byte[] bytes = str.getBytes();

            if(bytes.length <= limit){
                num = 0;
                flag = FULL_PACKAGE_FLAG;
                byte[] tempBytes  = new byte[bytes.length + 2];
                tempBytes[0] = (byte) (seq & 0xff);
                tempBytes[1] = (byte) (((type & 0x03)<<6) | ((flag & 0x03) << 4) | ((num & 0x0f)));
                System.arraycopy(bytes, 0, tempBytes, 2, bytes.length);

                arrayList.add(tempBytes);
            }else{
                List<byte[]> fragmentArrayList = fragmentByte(bytes, limit);
                if(fragmentArrayList != null && fragmentArrayList.size() > 0){
                    num = 0;
                    for(byte[] fragmentByte : fragmentArrayList){
                        if(num == 0){
                            flag = START_PACKAGE_FLAG;
                        }else if(num == (fragmentArrayList.size() -1)){
                            flag = END_PACKAGE_FLAG;
                        }else{
                            flag = MIDDLE_PACKAGE_FLAG;
                        }
                        byte[] tempBytes  = new byte[fragmentByte.length + 2];
                        tempBytes[0] = (byte) (seq & 0xff);
                        tempBytes[1] = (byte) (((type & 0x03)<<6) | ((flag & 0x03) << 4) | ((num & 0x0f)));
                        System.arraycopy(fragmentByte, 0, tempBytes, 2, fragmentByte.length);

                        arrayList.add(tempBytes);

                        num++;
                    }
                }
            }
        }
        return  arrayList;
    }

    public void onReceiveData(byte[] data) {
        if(data != null && data.length > 2){
            byte[] headByte = new byte[2];
            System.arraycopy(data, 0, headByte, 0, headByte.length);
            byte[] contentByte = new byte[data.length -2];
            System.arraycopy(data, 2, contentByte, 0, contentByte.length);

            byte seq = (byte) (headByte[0] & 0xff);
            byte type = (byte) ((headByte[1] & 0xc0) >> 6);
            byte flag = (byte) ((headByte[1] & 0x30) >> 4);
            byte num = (byte) (headByte[1] & 0x0f);

            Map<String, List<byte[]>> cacheMap = null;
            if(type == NOTIFY_TYPE){
                cacheMap = wl400WifiCacheMap;
            }else if(type == REQUEST_TYPE){
                cacheMap = wl400ConfigCacheMap;
            }else if(type == RESPONSE_TYPE){
                cacheMap = wl400AckCacheMap;
            }

            List<byte[]> seqList = cacheMap.get(""+seq);
            Wl400BlePackage wl400BlePackage = null;
            if(flag == FULL_PACKAGE_FLAG){
                try {
                    wl400BlePackage = new Wl400BlePackage(seq, type, flag, num, new String(contentByte, "UTF-8"));
                } catch (UnsupportedEncodingException e) {
                    logger.d( "ByteToString#ex: " + e);
                }
            }else if(flag == END_PACKAGE_FLAG){
                if(seqList != null && seqList.size() > 0){
                    try {
                        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                        for(byte[] bytes : seqList){
                            outputStream.write(bytes);
                        }
                        outputStream.write(contentByte);
                        byte[] output = outputStream.toByteArray();
                        outputStream.close();
                        wl400BlePackage = new Wl400BlePackage(seq, type, flag, num, new String(output, "UTF-8"));
                    }catch (Exception ex){
                        logger.d( "ByteToString#ex " + ex);
                    }
                    cacheMap.remove(""+seq);
                }
            }else{
                if(cacheMap != null){
                    if(seqList != null){
                        seqList.add(contentByte);
                    }else{
                        seqList = new ArrayList<>();
                        seqList.add(contentByte);
                    }
                    cacheMap.put(""+seq, seqList);
                }
            }

            if(wl400BlePackage != null){
                logger.d( "wl400BlePackage: " + wl400BlePackage + " type:" + type);
                if(type == NOTIFY_TYPE){
                    if(bleReceiveMessage != null){
                        bleReceiveMessage.onNotify(wl400BlePackage.getConfigType(), wl400BlePackage.getConfigBodyStr());
                    }
                }else if(type == REQUEST_TYPE){
                    if(bleReceiveMessage != null ){
                        bleReceiveMessage.onRequest(wl400BlePackage.getConfigType(), wl400BlePackage.getConfigBodyStr());
                    }
                }else if(type == RESPONSE_TYPE){
                    if(bleReceiveMessage != null ){
                        bleReceiveMessage.onResponse(wl400BlePackage.getConfigType());
                    }
                }
            }
        }else{
            logger.d( "onReceiveBleData#Data is null or length < 2 !");
        }
    }

    public class Wl400BlePackage{
        byte t;
        byte seq;
        byte f;
        byte num;
        String c;

        public Wl400BlePackage(byte seq, byte type, byte flag, byte num,  String content) {
            this.seq = seq;
            this.t = type;
            this.f = flag;
            this.num = num;
            this.c = content;
        }

        public int getType(){
            return t;
        }

        public int getSeq(){
            return  seq;
        }

        public int getFlag() {
            return f;
        }

        public int getNum(){
            return num;
        }

        public int getHead(){
            int head = seq;
            head = ((head << 8) & 0xff00);
            return head + (t&0x3<<6) + (f&0x03<<4) + (num&0x0f);
        }

        public String getContent() {
            return c;
        }

        public int getConfigType(){
            try {
                if(!TextUtils.isEmpty(c)){
                    JSONObject object = new JSONObject(c);
                    int configType = object.getInt("t");
                    return configType;
                }
            }catch (Exception ex){
                logger.d( "getConfigType#ex: " + ex);
            }
            return -1;
        }

        public String getConfigBodyStr(){
            try {
                if(!TextUtils.isEmpty(c)){
                    JSONObject object = new JSONObject(c);
                    String body = object.getString("b");
                    return body;
                }
            }catch (Exception ex){
                logger.d( "getConfigBodyStr#ex: " + ex);
            }
            return "";
        }

        @Override
        public String toString() {
            return "Wl400BlePackage{" +
                    "type=" + t +
                    ", seq=" + seq +
                    ", flag=" + f +
                    ", num=" + num +
                    ", content=" + c +
                    '}';
        }
    }

    public interface  Wl400BleReceiveMessage{
        void onNotify(int type, String jsonStr);
        void onResponse(int type);
        void onRequest(int type, String jsonStr);
    }
}
